{
	"$schema": "https://shadcn-svelte.com/schema/registry-item.json",
	"name": "dia-text-reveal",
	"title": "Dia Text Reveal",
	"type": "registry:block",
	"description": "A sweeping gradient text reveal with viewport triggering and rotating text support.",
	"dependencies": [
		"motion-sv"
	],
	"files": [
		{
			"content": "<script lang=\"ts\">\n\timport { cn } from \"$UTILS$\";\n\timport {\n\t\tanimate,\n\t\tmotion,\n\t\tuseInView,\n\t\tuseMotionValue,\n\t\tuseReducedMotion,\n\t\tuseTransform,\n\t\ttype AnimationPlaybackControls,\n\t\ttype MotionHTMLAttributes,\n\t\ttype MotionProps,\n\t} from \"motion-sv\";\n\timport { untrack } from \"svelte\";\n\n\tconst DEFAULT_COLORS = [\"#c679c4\", \"#fa3d1d\", \"#ffb005\", \"#e1e1fe\", \"#0358f7\"];\n\tconst BAND_HALF = 17;\n\tconst SWEEP_START = -BAND_HALF;\n\tconst SWEEP_END = 100 + BAND_HALF;\n\n\ttype DiaTextRevealProps = Omit<\n\t\tMotionProps<\"span\"> & MotionHTMLAttributes<\"span\">,\n\t\t\"children\" | \"style\" | \"animate\" | \"transition\" | \"color\"\n\t> & {\n\t\ttext: string | string[];\n\t\tcolors?: string[];\n\t\ttextColor?: string;\n\t\tduration?: number;\n\t\tdelay?: number;\n\t\trepeat?: boolean;\n\t\trepeatDelay?: number;\n\t\ttriggerOnView?: boolean;\n\t\tonce?: boolean;\n\t\tclass?: string;\n\t\tfixedWidth?: boolean;\n\t};\n\n\tconst sweepEase = (t: number) => (t < 0.5 ? 4 * t ** 3 : 1 - (-2 * t + 2) ** 3 / 2);\n\n\tfunction buildGradient(pos: number, colors: string[], textColor: string) {\n\t\tconst bandStart = pos - BAND_HALF;\n\t\tconst bandEnd = pos + BAND_HALF;\n\n\t\tif (bandStart >= 100) {\n\t\t\treturn `linear-gradient(90deg, ${textColor}, ${textColor})`;\n\t\t}\n\n\t\tconst count = colors.length;\n\t\tconst parts: string[] = [];\n\n\t\tif (bandStart > 0) {\n\t\t\tparts.push(`${textColor} 0%`, `${textColor} ${bandStart.toFixed(2)}%`);\n\t\t}\n\n\t\tcolors.forEach((color, index) => {\n\t\t\tconst pct = count === 1 ? pos : bandStart + (index / (count - 1)) * BAND_HALF * 2;\n\t\t\tparts.push(`${color} ${pct.toFixed(2)}%`);\n\t\t});\n\n\t\tif (bandEnd < 100) {\n\t\t\tparts.push(`transparent ${bandEnd.toFixed(2)}%`, \"transparent 100%\");\n\t\t}\n\n\t\treturn `linear-gradient(90deg, ${parts.join(\", \")})`;\n\t}\n\n\tfunction measureWidths(element: HTMLElement, texts: string[]) {\n\t\tconst ghost = element.cloneNode() as HTMLElement;\n\n\t\tObject.assign(ghost.style, {\n\t\t\tposition: \"absolute\",\n\t\t\tvisibility: \"hidden\",\n\t\t\tpointerEvents: \"none\",\n\t\t\twidth: \"auto\",\n\t\t\twhiteSpace: \"nowrap\",\n\t\t});\n\n\t\telement.parentElement?.appendChild(ghost);\n\n\t\tconst widths = texts.map((text) => {\n\t\t\tghost.textContent = text;\n\t\t\treturn ghost.getBoundingClientRect().width;\n\t\t});\n\n\t\tghost.remove();\n\t\treturn widths;\n\t}\n\n\tlet {\n\t\ttext,\n\t\tcolors = DEFAULT_COLORS,\n\t\ttextColor = \"var(--foreground)\",\n\t\tduration = 1.5,\n\t\tdelay = 0,\n\t\trepeat = false,\n\t\trepeatDelay = 0.5,\n\t\ttriggerOnView = true,\n\t\tonce = true,\n\t\tclass: className,\n\t\tfixedWidth = false,\n\t\t...props\n\t}: DiaTextRevealProps = $props();\n\n\tlet spanRef = $state<HTMLSpanElement | null>(null);\n\tlet activeIndex = $state(0);\n\tlet measuredWidths = $state<number[]>([]);\n\n\tlet indexRef = 0;\n\tlet hasPlayed = false;\n\tlet timerRef: ReturnType<typeof setTimeout> | undefined;\n\tlet controls: AnimationPlaybackControls | undefined;\n\tlet previousTextKey = \"\";\n\n\tconst sweepPos = useMotionValue(SWEEP_START);\n\tconst prefersReducedMotion = useReducedMotion();\n\tconst view = useInView(\n\t\t() => (triggerOnView ? spanRef : null)!,\n\t\t() => ({ once, amount: 0.1 }) as any\n\t);\n\n\tconst texts = $derived(Array.isArray(text) ? text : [text]);\n\tconst textKey = $derived(texts.join(\"\\0\"));\n\tconst isMulti = $derived(texts.length > 1);\n\tconst isInView = $derived(triggerOnView ? view.current : true);\n\n\tconst backgroundImage = useTransform(sweepPos, (pos) => buildGradient(pos, colors, textColor));\n\n\tconst fixedW = $derived(\n\t\tisMulti && fixedWidth && measuredWidths.length > 0 ? Math.max(...measuredWidths) : undefined\n\t);\n\n\tconst animatedW = $derived(\n\t\tisMulti && !fixedWidth && measuredWidths[activeIndex] != null\n\t\t\t? measuredWidths[activeIndex]\n\t\t\t: undefined\n\t);\n\n\tconst rootStyle = $derived.by(() => ({\n\t\ttransform: \"translateY(-2px)\",\n\t\tcolor: \"transparent\",\n\t\tbackgroundClip: \"text\",\n\t\tWebkitBackgroundClip: \"text\",\n\t\tbackgroundSize: \"100% 100%\",\n\t\tbackgroundImage,\n\t\t...(isMulti && {\n\t\t\tdisplay: \"inline-block\",\n\t\t\toverflow: \"hidden\",\n\t\t\twhiteSpace: \"nowrap\",\n\t\t\tverticalAlign: \"text-center\",\n\t\t\t...(fixedW != null && { width: fixedW }),\n\t\t}),\n\t}));\n\n\tfunction clearCycle() {\n\t\tcontrols?.stop();\n\t\tcontrols = undefined;\n\t\tclearTimeout(timerRef);\n\t\ttimerRef = undefined;\n\t}\n\n\tfunction play() {\n\t\tclearCycle();\n\t\tsweepPos.set(SWEEP_START);\n\n\t\tcontrols = animate(sweepPos, SWEEP_END, {\n\t\t\tduration,\n\t\t\tdelay,\n\t\t\tease: sweepEase,\n\t\t\tonComplete() {\n\t\t\t\tif (!repeat || texts.length === 0) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\ttimerRef = setTimeout(() => {\n\t\t\t\t\tconst next = (indexRef + 1) % texts.length;\n\t\t\t\t\tindexRef = next;\n\t\t\t\t\tactiveIndex = next;\n\t\t\t\t\tplay();\n\t\t\t\t}, repeatDelay * 1000);\n\t\t\t},\n\t\t});\n\t}\n\n\t$effect(() => {\n\t\tif (textKey === previousTextKey) {\n\t\t\treturn;\n\t\t}\n\n\t\tuntrack(() => {\n\t\t\tpreviousTextKey = textKey;\n\t\t\tindexRef = 0;\n\t\t\tactiveIndex = 0;\n\t\t\thasPlayed = false;\n\t\t\tclearCycle();\n\t\t\tsweepPos.set(SWEEP_START);\n\t\t});\n\t});\n\n\t$effect(() => {\n\t\tconst element = spanRef;\n\t\tconst shouldMeasure = isMulti;\n\t\ttextKey;\n\n\t\tif (!element || !shouldMeasure) {\n\t\t\tuntrack(() => {\n\t\t\t\tmeasuredWidths = [];\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tuntrack(() => {\n\t\t\tmeasuredWidths = measureWidths(element, texts);\n\t\t});\n\t});\n\n\t$effect(() => {\n\t\tconst reducedMotion = prefersReducedMotion.current;\n\t\tconst visible = isInView;\n\t\tconst playOnce = once;\n\n\t\tif (reducedMotion) {\n\t\t\tuntrack(() => {\n\t\t\t\tclearCycle();\n\t\t\t\tsweepPos.set(SWEEP_END);\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tif (!visible) {\n\t\t\tuntrack(() => {\n\t\t\t\tif (!playOnce) {\n\t\t\t\t\thasPlayed = false;\n\t\t\t\t}\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tconst alreadyPlayed = untrack(() => hasPlayed);\n\n\t\tif (playOnce && alreadyPlayed) {\n\t\t\treturn;\n\t\t}\n\n\t\tuntrack(() => {\n\t\t\thasPlayed = true;\n\t\t\tplay();\n\t\t});\n\n\t\treturn clearCycle;\n\t});\n</script>\n\n<motion.span\n\tbind:ref={spanRef}\n\tclass={cn(\"align-bottom leading-[100%] text-inherit\", className)}\n\tstyle={rootStyle}\n\tanimate={animatedW != null ? { width: animatedW } : undefined}\n\ttransition={{ duration: 0.4, ease: [0.4, 0, 0.2, 1] }}\n\t{...props}\n>\n\t{texts[activeIndex]}\n</motion.span>\n",
			"type": "registry:component",
			"target": "magic/dia-text-reveal/dia-text-reveal.svelte"
		},
		{
			"content": "import DiaTextReveal from \"./dia-text-reveal.svelte\";\nexport { DiaTextReveal };\n",
			"type": "registry:file",
			"target": "magic/dia-text-reveal/index.ts"
		}
	]
}