{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dia-text-reveal",
  "type": "registry:block",
  "title": "Dia text reveal",
  "description": "Dia text reveal",
  "files": [
    {
      "path": "components/usages/diatextrevealusage.tsx",
      "content": "import { DiaTextReveal } from \"@/registry/open-source/dia-text-reveal\"\n\nexport default function Usage() {\n    return (\n        <div className=\"flex min-h-56 items-center justify-center p-8\">\n            <DiaTextReveal\n                className=\"text-4xl font-bold tracking-tight\"\n                colors={[\"#f97316\", \"#eab308\", \"#22c55e\", \"#3b82f6\", \"#a855f7\"]}\n                delay={0.35}\n                duration={2.4}\n                text=\"Made with care\"\n                textColor=\"black\"\n            />\n            {/* <h1 className=\"text-center text-3xl font-semibold tracking-tight md:text-4xl\">\n                Learn to{\" \"}\n                <DiaTextReveal\n                    repeat\n                    repeatDelay={1.2}\n                    text={[\"build faster\", \"ship smarter\", \"scale easier\"]}\n                />\n            </h1>\n            <DiaTextReveal\n                className=\"text-4xl font-bold tracking-tight\"\n                colors={[\"#22d3ee\", \"#818cf8\", \"#f472b6\", \"#34d399\"]}\n                text=\"Design systems\"\n            /> */}\n        </div>\n    )\n}\n\n",
      "type": "registry:block",
      "target": "~/example.tsx"
    },
    {
      "path": "components/usages/diatextrevealusage.tsx",
      "content": "import { DiaTextReveal } from \"@/registry/open-source/dia-text-reveal\"\n\nexport default function Usage() {\n    return (\n        <div className=\"flex min-h-56 items-center justify-center p-8\">\n            <DiaTextReveal\n                className=\"text-4xl font-bold tracking-tight\"\n                colors={[\"#f97316\", \"#eab308\", \"#22c55e\", \"#3b82f6\", \"#a855f7\"]}\n                delay={0.35}\n                duration={2.4}\n                text=\"Made with care\"\n                textColor=\"black\"\n            />\n            {/* <h1 className=\"text-center text-3xl font-semibold tracking-tight md:text-4xl\">\n                Learn to{\" \"}\n                <DiaTextReveal\n                    repeat\n                    repeatDelay={1.2}\n                    text={[\"build faster\", \"ship smarter\", \"scale easier\"]}\n                />\n            </h1>\n            <DiaTextReveal\n                className=\"text-4xl font-bold tracking-tight\"\n                colors={[\"#22d3ee\", \"#818cf8\", \"#f472b6\", \"#34d399\"]}\n                text=\"Design systems\"\n            /> */}\n        </div>\n    )\n}\n\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/open-source/dia-text-reveal.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useRef, useState } from \"react\"\nimport {\n    animate,\n    motion,\n    useInView,\n    useMotionValue,\n    useReducedMotion,\n    useTransform,\n    type HTMLMotionProps,\n} from \"motion/react\"\nimport { cn } from \"../utilities/cn\"\n\n// Credit:\n// https://magicui.design/docs/components/dia-text-reveal\n\nconst DEFAULT_COLORS = [\"#c679c4\", \"#fa3d1d\", \"#ffb005\", \"#e1e1fe\", \"#0358f7\"]\nconst BAND_HALF = 17\nconst SWEEP_START = -BAND_HALF\nconst SWEEP_END = 100 + BAND_HALF\n\nconst sweepEase = (t: number) =>\n    t < 0.5 ? 4 * t ** 3 : 1 - (-2 * t + 2) ** 3 / 2\n\nfunction buildGradient(pos: number, colors: string[], textColor: string) {\n    const bandStart = pos - BAND_HALF\n    const bandEnd = pos + BAND_HALF\n\n    if (bandStart >= 100) {\n        return `linear-gradient(90deg, ${textColor}, ${textColor})`\n    }\n    const n = colors.length\n    const parts: string[] = []\n\n    if (bandStart > 0)\n        parts.push(`${textColor} 0%`, `${textColor} ${bandStart.toFixed(2)}%`)\n\n    colors.forEach((c, i) => {\n        const pct = n === 1 ? pos : bandStart + (i / (n - 1)) * BAND_HALF * 2\n        parts.push(`${c} ${pct.toFixed(2)}%`)\n    })\n\n    if (bandEnd < 100)\n        parts.push(`transparent ${bandEnd.toFixed(2)}%`, `transparent 100%`)\n\n    return `linear-gradient(90deg, ${parts.join(\", \")})`\n}\n\nfunction measureWidths(el: HTMLElement, texts: string[]) {\n    const ghost = el.cloneNode() as HTMLElement\n    Object.assign(ghost.style, {\n        position: \"absolute\",\n        visibility: \"hidden\",\n        pointerEvents: \"none\",\n        width: \"auto\",\n        whiteSpace: \"nowrap\",\n    })\n    el.parentElement!.appendChild(ghost)\n    const widths = texts.map((t) => {\n        ghost.textContent = t\n        return ghost.getBoundingClientRect().width\n    })\n    ghost.remove()\n    return widths\n}\n\n/**\n * Props for {@link DiaTextReveal}.\n */\nexport interface DiaTextRevealProps extends Omit<\n    HTMLMotionProps<\"span\">,\n    \"ref\" | \"children\" | \"style\" | \"animate\" | \"transition\" | \"color\"\n> {\n    /**\n     * Text to reveal. Pass multiple strings to rotate when {@link DiaTextRevealProps.repeat} is `true`.\n     */\n    text: string | string[]\n    /**\n     * Colors sampled across the moving gradient band. Defaults to a built-in palette.\n     */\n    colors?: string[]\n    /**\n     * CSS color for revealed text after the sweep and for leading/trailing regions during the animation.\n     * @defaultValue `\"var(--foreground)\"`\n     */\n    textColor?: string\n    /**\n     * Duration of one sweep pass, in seconds.\n     * @defaultValue `1.5`\n     */\n    duration?: number\n    /**\n     * Delay before the sweep starts, in seconds.\n     * @defaultValue `0`\n     */\n    delay?: number\n    /**\n     * When `text` is an array, replay the sweep and advance to the next string after each completion.\n     * @defaultValue `false`\n     */\n    repeat?: boolean\n    /**\n     * Pause between cycles when {@link DiaTextRevealProps.repeat} is `true`, in seconds.\n     * @defaultValue `0.5`\n     */\n    repeatDelay?: number\n    /**\n     * If `true`, the animation starts only after the element enters the viewport.\n     * @defaultValue `true`\n     */\n    startOnView?: boolean\n    /**\n     * Passed to `useInView`: if `true`, in-view detection fires at most once (no replay on scroll-back).\n     * @defaultValue `true`\n     */\n    once?: boolean\n    /**\n     * Additional class names for the animated `span` (e.g. typography utilities).\n     */\n    className?: string\n    /**\n     * When `text` has multiple entries, use the widest string’s width for layout instead of animating width per line.\n     * @defaultValue `false`\n     */\n    fixedWidth?: boolean\n}\n\nexport function DiaTextReveal({\n    text,\n    colors = DEFAULT_COLORS,\n    textColor = \"var(--foreground)\",\n    duration = 1.5,\n    delay = 0,\n    repeat = false,\n    repeatDelay = 0.5,\n    startOnView = true,\n    once = true,\n    className,\n    fixedWidth = false,\n    ...props\n}: DiaTextRevealProps) {\n    const texts = Array.isArray(text) ? text : [text]\n    const isMulti = texts.length > 1\n    const prefersReducedMotion = useReducedMotion()\n\n    const spanRef = useRef<HTMLSpanElement>(null)\n    const optsRef = useRef({\n        colors,\n        textColor,\n        duration,\n        delay,\n        repeat,\n        repeatDelay,\n        texts,\n    })\n    optsRef.current = {\n        colors,\n        textColor,\n        duration,\n        delay,\n        repeat,\n        repeatDelay,\n        texts,\n    }\n\n    const indexRef = useRef(0)\n    const hasPlayedRef = useRef(false)\n    const timerRef = useRef<ReturnType<typeof setTimeout>>(undefined)\n    const playRef = useRef<() => void>(null!)\n    const stopRef = useRef<(() => void) | null>(null)\n\n    const [activeIndex, setActiveIndex] = useState(0)\n    const [measuredWidths, setMeasuredWidths] = useState<number[]>([])\n\n    const sweepPos = useMotionValue(SWEEP_START)\n\n    const backgroundImage = useTransform(sweepPos, (pos) =>\n        buildGradient(pos, optsRef.current.colors, optsRef.current.textColor)\n    )\n\n    const isInView = useInView(spanRef, { once, amount: 0.1 })\n\n    useEffect(() => {\n        const el = spanRef.current\n        if (!el || !isMulti) return\n        setMeasuredWidths(measureWidths(el, texts))\n    }, [Array.isArray(text) ? text.join(\"\\0\") : text])\n\n    playRef.current = () => {\n        const { duration, delay, repeat, repeatDelay, texts } = optsRef.current\n\n        sweepPos.set(SWEEP_START)\n\n        const controls = animate(sweepPos, SWEEP_END, {\n            duration,\n            delay,\n            ease: sweepEase,\n            onComplete() {\n                if (!repeat) return\n                timerRef.current = setTimeout(() => {\n                    const next = (indexRef.current + 1) % texts.length\n                    indexRef.current = next\n                    setActiveIndex(next)\n                    playRef.current()\n                }, repeatDelay * 1000)\n            },\n        })\n\n        stopRef.current = () => controls.stop()\n    }\n\n    useEffect(() => {\n        if (prefersReducedMotion) {\n            sweepPos.set(SWEEP_END)\n            return\n        }\n        if (startOnView && !isInView) return\n        if (once && hasPlayedRef.current) return\n        hasPlayedRef.current = true\n        playRef.current()\n\n        return () => {\n            stopRef.current?.()\n            clearTimeout(timerRef.current)\n        }\n    }, [isInView, startOnView, once, prefersReducedMotion, sweepPos])\n\n    const fixedW =\n        isMulti && fixedWidth && measuredWidths.length > 0\n            ? Math.max(...measuredWidths)\n            : undefined\n\n    const animatedW =\n        isMulti && !fixedWidth && measuredWidths[activeIndex] != null\n            ? measuredWidths[activeIndex]\n            : undefined\n\n    return (\n        <motion.span\n            ref={spanRef}\n            className={cn(\"align-bottom leading-[100%] text-inherit\", className)}\n            style={{\n                transform: \"translateY(-2px)\",\n                color: \"transparent\",\n                backgroundClip: \"text\",\n                WebkitBackgroundClip: \"text\",\n                backgroundSize: \"100% 100%\",\n                backgroundImage,\n                ...(isMulti && {\n                    display: \"inline-block\",\n                    overflow: \"hidden\",\n                    whiteSpace: \"nowrap\",\n                    verticalAlign: \"text-center\",\n                    ...(fixedW != null && { width: fixedW }),\n                }),\n            }}\n            animate={animatedW != null ? { width: animatedW } : undefined}\n            transition={{ duration: 0.4, ease: [0.4, 0, 0.2, 1] }}\n            {...props}\n        >\n            {texts[activeIndex]}\n        </motion.span>\n    )\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/utilities/cn.ts",
      "content": "import { ClassValue, clsx } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\n\r\nexport function cn(...inputs: ClassValue[]) {\r\n\treturn twMerge(clsx(inputs));\r\n}\r\n",
      "type": "registry:ui"
    }
  ]
}