{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "terminal1",
  "title": "CLI Installation Typewriter",
  "description": "Animated terminal that types commands character-by-character with output responses. Perfect for showcasing CLI installation steps or quick-start guides.",
  "dependencies": [
    "clsx",
    "tailwind-merge",
    "lucide-react"
  ],
  "registryDependencies": [
    "badge",
    "button"
  ],
  "files": [
    {
      "path": "registry/terminal1/terminal1.tsx",
      "content": "\"use client\";\n\nimport { Check, Copy, RotateCcw } from \"lucide-react\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\ninterface TerminalCommand {\n  id: string;\n  prompt?: string;\n  command: string;\n  output?: string;\n  outputDelay?: number;\n}\n\ninterface Terminal1Props {\n  badge?: {\n    label: string;\n    variant?: \"default\" | \"secondary\" | \"outline\";\n  };\n  heading?: string;\n  description?: string;\n  terminal?: {\n    title?: string;\n    commands: TerminalCommand[];\n    typeSpeed?: number;\n    delayBetweenCommands?: number;\n    showLineNumbers?: boolean;\n  };\n  showCopyButton?: boolean;\n  glowEffect?: boolean;\n  className?: string;\n}\n\nexport const terminal1Demo: Terminal1Props = {\n  badge: { label: \"Quick Start\", variant: \"secondary\" },\n  heading: \"Get started in seconds\",\n  description:\n    \"Install our CLI and start building. Just a few commands to get up and running.\",\n  terminal: {\n    title: \"Terminal\",\n    commands: [\n      {\n        id: \"cmd-1\",\n        prompt: \"$ \",\n        command: \"npm install -g @acme/cli\",\n        output: \"Installing @acme/cli...\\n✓ Installed successfully\",\n        outputDelay: 800,\n      },\n      {\n        id: \"cmd-2\",\n        prompt: \"$ \",\n        command: \"acme init my-project\",\n        output: \"Creating project structure...\\n✓ Project initialized\",\n        outputDelay: 600,\n      },\n      {\n        id: \"cmd-3\",\n        prompt: \"$ \",\n        command: \"cd my-project && acme dev\",\n        output:\n          \"Starting development server...\\n✓ Ready at http://localhost:3000\",\n        outputDelay: 500,\n      },\n    ],\n    typeSpeed: 50,\n    delayBetweenCommands: 1000,\n    showLineNumbers: false,\n  },\n  showCopyButton: true,\n  glowEffect: true,\n};\n\ninterface TypewriterState {\n  commandIndex: number;\n  charIndex: number;\n  outputCharIndex: number;\n  isTypingCommand: boolean;\n  isTypingOutput: boolean;\n  isComplete: boolean;\n}\n\nfunction useTerminalTypewriter(\n  commands: TerminalCommand[],\n  typeSpeed = 50,\n  outputTypeSpeed = 10,\n  delayBetweenCommands = 1000,\n  isInView = true\n) {\n  const [state, setState] = useState<TypewriterState>({\n    commandIndex: 0,\n    charIndex: 0,\n    outputCharIndex: 0,\n    isTypingCommand: true,\n    isTypingOutput: false,\n    isComplete: false,\n  });\n  const [displayedCommands, setDisplayedCommands] = useState<\n    Array<{ command: string; output: string; isComplete: boolean }>\n  >([]);\n  const [isStarted, setIsStarted] = useState(false);\n\n  const reset = useCallback(() => {\n    setState({\n      commandIndex: 0,\n      charIndex: 0,\n      outputCharIndex: 0,\n      isTypingCommand: true,\n      isTypingOutput: false,\n      isComplete: false,\n    });\n    setDisplayedCommands([]);\n    setIsStarted(false);\n  }, []);\n\n  useEffect(() => {\n    if (isInView && !isStarted) {\n      setIsStarted(true);\n    }\n  }, [isInView, isStarted]);\n\n  useEffect(() => {\n    if (!isStarted || !commands || commands.length === 0 || state.isComplete)\n      return;\n\n    const currentCommand = commands[state.commandIndex];\n    if (!currentCommand) return;\n\n    // Typing command\n    if (state.isTypingCommand) {\n      if (state.charIndex < currentCommand.command.length) {\n        const timeout = setTimeout(() => {\n          setState((prev) => ({ ...prev, charIndex: prev.charIndex + 1 }));\n\n          setDisplayedCommands((prev) => {\n            const updated = [...prev];\n            const existing = updated[state.commandIndex] || {\n              command: \"\",\n              output: \"\",\n              isComplete: false,\n            };\n            updated[state.commandIndex] = {\n              command: currentCommand.command.slice(0, state.charIndex + 1),\n              output: existing.output,\n              isComplete: false,\n            };\n            return updated;\n          });\n        }, typeSpeed);\n        return () => clearTimeout(timeout);\n      } else {\n        // Command finished, start output after delay\n        const outputDelay = currentCommand.outputDelay ?? 500;\n        const timeout = setTimeout(() => {\n          setState((prev) => ({\n            ...prev,\n            isTypingCommand: false,\n            isTypingOutput: !!currentCommand.output,\n            outputCharIndex: 0,\n          }));\n        }, outputDelay);\n        return () => clearTimeout(timeout);\n      }\n    }\n\n    // Typing output\n    if (state.isTypingOutput && currentCommand.output) {\n      if (state.outputCharIndex < currentCommand.output.length) {\n        const timeout = setTimeout(() => {\n          setState((prev) => ({\n            ...prev,\n            outputCharIndex: prev.outputCharIndex + 1,\n          }));\n\n          setDisplayedCommands((prev) => {\n            const updated = [...prev];\n            const existing = updated[state.commandIndex] || {\n              command: \"\",\n              output: \"\",\n              isComplete: false,\n            };\n            updated[state.commandIndex] = {\n              command: existing.command,\n              output:\n                currentCommand.output?.slice(0, state.outputCharIndex + 1) ||\n                \"\",\n              isComplete: false,\n            };\n            return updated;\n          });\n        }, outputTypeSpeed);\n        return () => clearTimeout(timeout);\n      } else {\n        // Output finished\n        setDisplayedCommands((prev) => {\n          const updated = [...prev];\n          const existing = updated[state.commandIndex] || {\n            command: \"\",\n            output: \"\",\n            isComplete: false,\n          };\n          updated[state.commandIndex] = {\n            command: existing.command,\n            output: currentCommand.output || \"\",\n            isComplete: true,\n          };\n          return updated;\n        });\n        setState((prev) => ({ ...prev, isTypingOutput: false }));\n      }\n    }\n\n    // Move to next command or finish\n    if (!state.isTypingCommand && !state.isTypingOutput) {\n      if (state.commandIndex < commands.length - 1) {\n        const timeout = setTimeout(() => {\n          setState((prev) => ({\n            ...prev,\n            commandIndex: prev.commandIndex + 1,\n            charIndex: 0,\n            outputCharIndex: 0,\n            isTypingCommand: true,\n            isTypingOutput: false,\n          }));\n        }, delayBetweenCommands);\n        return () => clearTimeout(timeout);\n      } else {\n        setState((prev) => ({ ...prev, isComplete: true }));\n      }\n    }\n  }, [\n    state,\n    commands,\n    typeSpeed,\n    outputTypeSpeed,\n    delayBetweenCommands,\n    isStarted,\n  ]);\n\n  return {\n    displayedCommands,\n    currentCommandIndex: state.commandIndex,\n    isTypingCommand: state.isTypingCommand && !state.isComplete,\n    isTypingOutput: state.isTypingOutput && !state.isComplete,\n    isComplete: state.isComplete,\n    reset,\n  };\n}\n\nconst terminalTheme = {\n  bg: \"#09090b\", // zinc-900\n  border: \"#3f3f46\", // zinc-700\n  header: \"#27272a\", // zinc-800\n  headerText: \"#a1a1aa\", // zinc-400\n  text: \"#f4f4f5\", // zinc-100\n  prompt: \"#34d399\", // emerald-400\n  output: \"#a1a1aa\", // zinc-400\n  glow: \"0 0 50px rgba(0,0,0,0.5)\",\n};\n\nfunction useInView(\n  ref: React.RefObject<HTMLElement | null>,\n  options?: { amount?: number }\n) {\n  const [isInView, setIsInView] = useState(false);\n\n  useEffect(() => {\n    if (!ref.current) return;\n\n    const observer = new IntersectionObserver(\n      ([entry]) => {\n        if (entry?.isIntersecting) {\n          setIsInView(true);\n        }\n      },\n      { threshold: options?.amount ?? 0.3 }\n    );\n\n    observer.observe(ref.current);\n    return () => observer.disconnect();\n  }, [ref, options?.amount]);\n\n  return isInView;\n}\n\nexport function Terminal1({\n  badge,\n  heading,\n  description,\n  terminal,\n  showCopyButton = true,\n  glowEffect = true,\n  className,\n}: Terminal1Props) {\n  const containerRef = useRef<HTMLDivElement>(null);\n  const isInView = useInView(containerRef, { amount: 0.3 });\n  const [copied, setCopied] = useState(false);\n\n  const {\n    displayedCommands,\n    isTypingCommand,\n    isTypingOutput,\n    isComplete,\n    reset,\n  } = useTerminalTypewriter(\n    terminal?.commands || [],\n    terminal?.typeSpeed || 50,\n    15,\n    terminal?.delayBetweenCommands || 1000,\n    isInView\n  );\n\n  const handleCopy = useCallback(() => {\n    const allCommands = terminal?.commands\n      .map((cmd) => `${cmd.prompt || \"$ \"}${cmd.command}`)\n      .join(\"\\n\");\n\n    if (allCommands) {\n      navigator.clipboard.writeText(allCommands);\n      setCopied(true);\n      setTimeout(() => setCopied(false), 2000);\n    }\n  }, [terminal?.commands]);\n\n  const handleRestart = useCallback(() => {\n    reset();\n  }, [reset]);\n\n  return (\n    <section className={cn(\"py-16 md:py-24 w-full\", className)}>\n      <div className=\"mx-auto max-w-4xl px-4 md:px-6\">\n        <div className=\"flex gap-4 items-center justify-center flex-col\">\n          {badge && (\n            <div>\n              <Badge variant={badge.variant ?? \"default\"}>{badge.label}</Badge>\n            </div>\n          )}\n\n          {heading && (\n            <h2 className=\"text-2xl md:text-4xl text-center font-semibold\">\n              {heading}\n            </h2>\n          )}\n\n          {description && (\n            <p className=\"text-base md:text-lg text-balance text-center max-w-3xl text-muted-foreground\">\n              {description}\n            </p>\n          )}\n\n          <div ref={containerRef} className=\"w-full mt-8\">\n            <div\n              className=\"rounded-xl overflow-hidden\"\n              style={{\n                backgroundColor: terminalTheme.bg,\n                border: `1px solid ${terminalTheme.border}`,\n                boxShadow: glowEffect ? terminalTheme.glow : undefined,\n              }}\n            >\n              {/* Header with 3-column grid for centered title */}\n              <div\n                className=\"grid grid-cols-3 items-center px-4 py-3\"\n                style={{ backgroundColor: terminalTheme.header }}\n              >\n                {/* Left: Window controls */}\n                <div className=\"flex items-center gap-2\">\n                  <div className=\"w-3 h-3 rounded-full bg-[#ff5f56]\" />\n                  <div className=\"w-3 h-3 rounded-full bg-[#ffbd2e]\" />\n                  <div className=\"w-3 h-3 rounded-full bg-[#27c93f]\" />\n                </div>\n\n                {/* Center: Title (always centered) */}\n                <span\n                  className=\"text-sm font-medium text-center\"\n                  style={{ color: terminalTheme.headerText }}\n                >\n                  {terminal?.title || \"Terminal\"}\n                </span>\n\n                {/* Right: Action buttons */}\n                <div className=\"flex items-center gap-2 justify-end\">\n                  {showCopyButton && (\n                    <Button\n                      variant=\"ghost\"\n                      size=\"icon\"\n                      onClick={handleCopy}\n                      className=\"w-6 h-6 rounded-md transition-colors hover:bg-white/10\"\n                      style={{ color: terminalTheme.headerText }}\n                      title=\"Copy commands\"\n                    >\n                      {copied ? (\n                        <Check className=\"size-4 text-emerald-400\" />\n                      ) : (\n                        <Copy className=\"size-4\" />\n                      )}\n                    </Button>\n                  )}\n                  {isComplete && (\n                    <Button\n                      variant=\"ghost\"\n                      size=\"icon\"\n                      onClick={handleRestart}\n                      className=\"w-6 h-6 rounded-md transition-colors hover:bg-white/10\"\n                      style={{ color: terminalTheme.headerText }}\n                      title=\"Restart animation\"\n                    >\n                      <RotateCcw className=\"size-4\" />\n                    </Button>\n                  )}\n                </div>\n              </div>\n\n              <div className=\"p-4 md:p-6 font-mono text-sm md:text-base min-h-[200px]\">\n                {displayedCommands.map((item, index) => {\n                  const command = terminal?.commands[index];\n                  const isCurrentCommand =\n                    index === displayedCommands.length - 1;\n                  return (\n                    <div key={command?.id || index} className=\"mb-3 last:mb-0\">\n                      <div className=\"flex items-start gap-2\">\n                        {terminal?.showLineNumbers && (\n                          <span\n                            className=\"select-none w-6 text-right\"\n                            style={{ color: terminalTheme.output }}\n                          >\n                            {index + 1}\n                          </span>\n                        )}\n                        <span\n                          className=\"font-semibold\"\n                          style={{ color: terminalTheme.prompt }}\n                        >\n                          {command?.prompt || \"$ \"}\n                        </span>\n                        <span style={{ color: terminalTheme.text }}>\n                          {item.command}\n                          {isCurrentCommand && isTypingCommand && (\n                            <span className=\"animate-pulse\">|</span>\n                          )}\n                        </span>\n                      </div>\n                      {item.output && (\n                        <div\n                          className={cn(\n                            \"mt-1 whitespace-pre-wrap\",\n                            terminal?.showLineNumbers && \"ml-8\"\n                          )}\n                          style={{ color: terminalTheme.output }}\n                        >\n                          {item.output}\n                          {isCurrentCommand && isTypingOutput && (\n                            <span className=\"animate-pulse\">|</span>\n                          )}\n                        </div>\n                      )}\n                    </div>\n                  );\n                })}\n                {displayedCommands.length === 0 && (\n                  <div className=\"flex items-center gap-2\">\n                    <span\n                      className=\"font-semibold\"\n                      style={{ color: terminalTheme.prompt }}\n                    >\n                      {terminal?.commands[0]?.prompt || \"$ \"}\n                    </span>\n                    <span\n                      className=\"animate-pulse\"\n                      style={{ color: terminalTheme.text }}\n                    >\n                      |\n                    </span>\n                  </div>\n                )}\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </section>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/beste/block/terminal1.tsx"
    }
  ],
  "type": "registry:block"
}