{
	"$schema": "https://shadcn-svelte.com/schema/registry-item.json",
	"name": "terminal",
	"title": "Terminal",
	"type": "registry:block",
	"description": "A terminal-like UI component with typing animations, sequence control, and intersection observer support.",
	"dependencies": [
		"runed"
	],
	"css": {
		"@keyframes blink-cursor": {
			"0%, 49%": {
				"opacity": "1"
			},
			"50%, 100%": {
				"opacity": "0"
			}
		}
	},
	"cssVars": {
		"theme": {
			"animate-blink-cursor": "blink-cursor 1.2s step-end infinite"
		}
	},
	"files": [
		{
			"content": "<script lang=\"ts\">\n\timport { onMount, setContext } from \"svelte\";\n\timport { cn } from \"$UTILS$\";\n\timport type { Snippet } from \"svelte\";\n\timport { watch } from \"runed\";\n\n\tinterface TerminalProps {\n\t\tchildren: Snippet;\n\t\tclass?: string;\n\t\tsequence?: boolean;\n\t\tstartOnView?: boolean;\n\t}\n\n\tlet {\n\t\tchildren,\n\t\tclass: className,\n\t\tsequence = true,\n\t\tstartOnView = true,\n\t}: TerminalProps = $props();\n\n\tlet containerRef: HTMLDivElement | null = $state(null);\n\tlet isInView = $state(false);\n\tlet activeIndex = $state(0);\n\n\tconst sequenceHasStarted = $derived(sequence ? !startOnView || isInView : false);\n\n\t// Set context for child components - always set it, check sequence inside\n\tlet useSequence = $derived(sequence);\n\twatch(\n\t\t() => useSequence,\n\t\t() => {\n\t\t\t// setContext(\"terminal-sequence\", val);\n\t\t\tsetContext(\n\t\t\t\t\"terminal-sequence\",\n\t\t\t\tuseSequence\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tget activeIndex() {\n\t\t\t\t\t\t\t\treturn activeIndex;\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tget sequenceStarted() {\n\t\t\t\t\t\t\t\treturn sequenceHasStarted;\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tcompleteItem: (index: number) => {\n\t\t\t\t\t\t\t\tif (index === activeIndex) {\n\t\t\t\t\t\t\t\t\tactiveIndex = activeIndex + 1;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\t\t\t\t\t: null\n\t\t\t);\n\t\t}\n\t);\n\n\tonMount(() => {\n\t\tif (!startOnView || !sequence) {\n\t\t\tisInView = true;\n\t\t\treturn;\n\t\t}\n\n\t\tif (!containerRef) return;\n\n\t\tconst observer = new IntersectionObserver(\n\t\t\t([entry]) => {\n\t\t\t\tif (entry.isIntersecting) {\n\t\t\t\t\tisInView = true;\n\t\t\t\t\tobserver.disconnect();\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\tthreshold: 0.3,\n\t\t\t}\n\t\t);\n\n\t\tobserver.observe(containerRef);\n\n\t\treturn () => observer.disconnect();\n\t});\n</script>\n\n<div\n\tbind:this={containerRef}\n\tclass={cn(\n\t\t\"border-border bg-background z-0 h-full max-h-[400px] w-full max-w-lg rounded-xl border\",\n\t\tclassName\n\t)}\n>\n\t<div class=\"border-border flex flex-col gap-y-2 border-b p-4\">\n\t\t<div class=\"flex flex-row gap-x-2\">\n\t\t\t<div class=\"h-2 w-2 rounded-full bg-red-500\"></div>\n\t\t\t<div class=\"h-2 w-2 rounded-full bg-yellow-500\"></div>\n\t\t\t<div class=\"h-2 w-2 rounded-full bg-green-500\"></div>\n\t\t</div>\n\t</div>\n\t<pre class=\"p-4\">\n    <code class=\"grid gap-y-1 overflow-auto\">{@render children()}</code>\n  </pre>\n</div>\n",
			"type": "registry:component",
			"target": "magic/terminal/terminal.svelte"
		},
		{
			"content": "<script lang=\"ts\">\n\timport { onMount, getContext } from \"svelte\";\n\timport { motion } from \"motion-sv\";\n\timport { cn } from \"$UTILS$\";\n\n\tinterface AnimatedSpanProps {\n\t\tchildren: Snippet;\n\t\tdelay?: number;\n\t\tclass?: string;\n\t\tstartOnView?: boolean;\n\t\titemIndex?: number;\n\t}\n\n\timport type { Snippet } from \"svelte\";\n\n\tlet {\n\t\tchildren,\n\t\tdelay = 0,\n\t\tclass: className,\n\t\tstartOnView = false,\n\t\titemIndex,\n\t}: AnimatedSpanProps = $props();\n\n\tlet containerRef: HTMLDivElement | null = $state(null);\n\tlet isInView = $state(false);\n\tlet hasStarted = $state(false);\n\n\t// Get sequence context if available\n\tlet sequence = getContext<{\n\t\tactiveIndex: number;\n\t\tsequenceStarted: boolean;\n\t\tcompleteItem: (index: number) => void;\n\t} | null>(\"terminal-sequence\");\n\n\tlet shouldAnimate = $derived(\n\t\tsequence && itemIndex !== undefined ? hasStarted : startOnView ? isInView : true\n\t);\n\n\tonMount(() => {\n\t\t// Handle IntersectionObserver for startOnView\n\t\tif (!sequence && startOnView && containerRef) {\n\t\t\tconst observer = new IntersectionObserver(\n\t\t\t\t([entry]) => {\n\t\t\t\t\tif (entry.isIntersecting) {\n\t\t\t\t\t\tisInView = true;\n\t\t\t\t\t\tobserver.disconnect();\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tthreshold: 0.3,\n\t\t\t\t}\n\t\t\t);\n\n\t\t\tobserver.observe(containerRef);\n\n\t\t\treturn () => observer.disconnect();\n\t\t} else if (!sequence && !startOnView) {\n\t\t\tisInView = true;\n\t\t}\n\t});\n\n\t// Watch for sequence changes\n\t$effect(() => {\n\t\tif (!sequence || itemIndex === undefined) return;\n\t\tif (!sequence.sequenceStarted) return;\n\t\tif (hasStarted) return;\n\t\tif (sequence.activeIndex === itemIndex) {\n\t\t\thasStarted = true;\n\t\t}\n\t});\n\n\tfunction handleAnimationComplete() {\n\t\tif (!sequence || itemIndex === undefined) return;\n\t\tsequence.completeItem(itemIndex);\n\t}\n</script>\n\n<div bind:this={containerRef}>\n\t<motion.div\n\t\tinitial={{ opacity: 0, y: -5 }}\n\t\tanimate={shouldAnimate ? { opacity: 1, y: 0 } : { opacity: 0, y: -5 }}\n\t\ttransition={{ duration: 0.3, delay: sequence ? 0 : delay / 1000 }}\n\t\tclass={cn(\"grid text-sm font-normal tracking-tight\", className)}\n\t\tonanimationend={handleAnimationComplete}\n\t>\n\t\t{@render children()}\n\t</motion.div>\n</div>\n",
			"type": "registry:component",
			"target": "magic/terminal/animated-span.svelte"
		},
		{
			"content": "<script lang=\"ts\">\n\timport { onMount, getContext } from \"svelte\";\n\timport { motion } from \"motion-sv\";\n\timport { cn } from \"$UTILS$\";\n\n\ttype ElementType = \"div\" | \"span\" | \"p\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\";\n\n\tinterface TypingAnimationProps {\n\t\ttext: string;\n\t\tclass?: string;\n\t\tduration?: number;\n\t\tdelay?: number;\n\t\tas?: ElementType;\n\t\tstartOnView?: boolean;\n\t\titemIndex?: number;\n\t}\n\n\tlet {\n\t\ttext,\n\t\tclass: className,\n\t\tduration = 60,\n\t\tdelay = 0,\n\t\tas = \"span\",\n\t\tstartOnView = true,\n\t\titemIndex,\n\t}: TypingAnimationProps = $props();\n\n\tlet MotionComponent = $derived(motion[as]);\n\n\tlet displayedText = $state(\"\");\n\tlet started = $state(false);\n\tlet containerRef: HTMLElement | null = $state(null);\n\tlet isInView = $state(false);\n\n\t// Get sequence context if available\n\tconst sequence = getContext<{\n\t\tactiveIndex: number;\n\t\tsequenceStarted: boolean;\n\t\tcompleteItem: (index: number) => void;\n\t} | null>(\"terminal-sequence\");\n\n\tonMount(() => {\n\t\t// Handle IntersectionObserver for startOnView\n\t\tif (!sequence && startOnView && containerRef) {\n\t\t\tconst observer = new IntersectionObserver(\n\t\t\t\t([entry]) => {\n\t\t\t\t\tif (entry.isIntersecting) {\n\t\t\t\t\t\tisInView = true;\n\t\t\t\t\t\tobserver.disconnect();\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tthreshold: 0.3,\n\t\t\t\t}\n\t\t\t);\n\n\t\t\tobserver.observe(containerRef);\n\n\t\t\treturn () => observer.disconnect();\n\t\t} else if (!sequence && !startOnView) {\n\t\t\tconst startTimeout = setTimeout(() => (started = true), delay);\n\t\t\treturn () => clearTimeout(startTimeout);\n\t\t}\n\t});\n\n\t// Watch for sequence or view changes to start animation\n\t$effect(() => {\n\t\tif (sequence && itemIndex !== undefined) {\n\t\t\tif (!sequence.sequenceStarted) return;\n\t\t\tif (started) return;\n\t\t\tif (sequence.activeIndex === itemIndex) {\n\t\t\t\tstarted = true;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (!startOnView) return;\n\t\tif (!isInView) return;\n\t\tif (started) return;\n\n\t\tconst startTimeout = setTimeout(() => (started = true), delay);\n\t\treturn () => clearTimeout(startTimeout);\n\t});\n\n\t// Typing effect\n\t$effect(() => {\n\t\tif (!started) return;\n\n\t\tlet i = 0;\n\t\tconst textToType = text; // Capture the value\n\t\tconst typingEffect = setInterval(() => {\n\t\t\tif (i < textToType.length) {\n\t\t\t\tdisplayedText = textToType.substring(0, i + 1);\n\t\t\t\ti++;\n\t\t\t} else {\n\t\t\t\tclearInterval(typingEffect);\n\t\t\t\tif (sequence && itemIndex !== undefined) {\n\t\t\t\t\tsequence.completeItem(itemIndex);\n\t\t\t\t}\n\t\t\t}\n\t\t}, duration);\n\n\t\treturn () => {\n\t\t\tclearInterval(typingEffect);\n\t\t};\n\t});\n</script>\n\n<div bind:this={containerRef}>\n\t<MotionComponent class={cn(\"text-sm font-normal tracking-tight\", className)}>\n\t\t{displayedText}\n\t</MotionComponent>\n</div>\n",
			"type": "registry:component",
			"target": "magic/terminal/typing-animation.svelte"
		},
		{
			"content": "export { default as Terminal } from \"./terminal.svelte\";\nexport { default as AnimatedSpan } from \"./animated-span.svelte\";\nexport { default as TypingAnimation } from \"./typing-animation.svelte\";\n",
			"type": "registry:file",
			"target": "magic/terminal/index.ts"
		}
	]
}