{
  "name": "letter-cascade",
  "type": "registry:ui",
  "dependencies": [],
  "registryDependencies": [],
  "description": "Component for letter-cascade",
  "files": [
    {
      "path": "components/ui/letter-cascade.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@workspace/ui/lib/utils\";\nimport {\n    type AnimationOptions,\n    motion,\n    stagger,\n    useAnimate,\n} from \"framer-motion\";\nimport { useCallback, useState } from \"react\";\n\ninterface LetterCascadeProps {\n    /** The text to animate */\n    text: string;\n    /** Additional CSS classes for the container */\n    className?: string;\n    /** CSS classes applied to each individual letter */\n    letterClassName?: string;\n    /** Stagger delay between each letter in seconds */\n    staggerDuration?: number;\n    /** Where the stagger wave originates */\n    staggerFrom?: \"first\" | \"last\" | \"center\" | number;\n    /** Spring stiffness — higher = snappier */\n    stiffness?: number;\n    /** Spring damping — lower = bouncier */\n    damping?: number;\n    /** Trigger the animation on click instead of hover */\n    triggerOnClick?: boolean;\n    /** Callback when the full animation cycle completes */\n    onComplete?: () => void;\n}\n\nexport function LetterCascade({\n    text,\n    className,\n    letterClassName,\n    staggerDuration = 0.04,\n    staggerFrom = \"first\",\n    stiffness = 220,\n    damping = 16,\n    triggerOnClick = false,\n    onComplete,\n}: LetterCascadeProps) {\n    const [scope, animate] = useAnimate();\n    const [blocked, setBlocked] = useState(false);\n\n    const trigger = useCallback(() => {\n        if (blocked) return;\n        setBlocked(true);\n\n        const merge = (base: AnimationOptions): AnimationOptions => ({\n            ...base,\n            delay: stagger(staggerDuration, { from: staggerFrom }),\n        });\n\n        const spring: AnimationOptions = {\n            type: \"spring\",\n            stiffness,\n            damping,\n        };\n\n        // ── Phase 1: front tilts back, echo flips in from below ──\n        animate(\n            \".cascade-front\",\n            {\n                rotateX: 90,\n                opacity: 0,\n                y: -6,\n                filter: \"blur(4px)\",\n            },\n            merge(spring)\n        ).then(() => {\n            // Instantly reset front\n            animate(\n                \".cascade-front\",\n                { rotateX: 0, opacity: 1, y: 0, filter: \"blur(0px)\" },\n                { duration: 0 }\n            ).then(() => {\n                setBlocked(false);\n                onComplete?.();\n            });\n        });\n\n        animate(\n            \".cascade-echo\",\n            {\n                rotateX: 0,\n                opacity: 1,\n                y: 0,\n                scale: 1,\n                filter: \"blur(0px)\",\n            },\n            merge(spring)\n        ).then(() => {\n            // Instantly reset echo\n            animate(\n                \".cascade-echo\",\n                {\n                    rotateX: -90,\n                    opacity: 0,\n                    y: 6,\n                    scale: 0.8,\n                    filter: \"blur(4px)\",\n                },\n                { duration: 0 }\n            );\n        });\n    }, [\n        blocked,\n        animate,\n        staggerDuration,\n        staggerFrom,\n        stiffness,\n        damping,\n        onComplete,\n    ]);\n\n    return (\n        <span\n            ref={scope}\n            className={cn(\n                \"inline-flex cursor-pointer select-none items-center justify-center\",\n                className\n            )}\n            {...(triggerOnClick ? { onClick: trigger } : { onMouseEnter: trigger })}\n            aria-label={text}\n        >\n            {text.split(\"\").map((letter, i) => (\n                <span\n                    key={i}\n                    className=\"relative inline-flex whitespace-pre\"\n                    style={{ perspective: \"500px\" }}\n                >\n                    {/* Front face — visible by default, tilts backward on trigger */}\n                    <motion.span\n                        className={cn(\"cascade-front inline-block\", letterClassName)}\n                        style={{\n                            rotateX: 0,\n                            y: 0,\n                            transformOrigin: \"bottom center\",\n                            backfaceVisibility: \"hidden\",\n                        }}\n                    >\n                        {letter}\n                    </motion.span>\n\n                    {/* Echo face — hidden below, flips up into view on trigger */}\n                    <motion.span\n                        className={cn(\n                            \"cascade-echo absolute inset-0 inline-block\",\n                            letterClassName\n                        )}\n                        style={{\n                            rotateX: -90,\n                            opacity: 0,\n                            y: 6,\n                            scale: 0.8,\n                            filter: \"blur(4px)\",\n                            transformOrigin: \"top center\",\n                            backfaceVisibility: \"hidden\",\n                        }}\n                    >\n                        {letter}\n                    </motion.span>\n                </span>\n            ))}\n        </span>\n    );\n}\n",
      "type": "registry:ui"
    }
  ]
}