{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cursor-terminal",
  "title": "Cursor Terminal",
  "description": "VS Code / Cursor panel mock with PowerShell prompt and a typed shadcn add flow for the hero-section-v1 registry block.",
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/new-york/blocks/cursor-terminal/page.tsx",
      "content": "import CursorTerminal from \"./components/cursor-terminal\"\n\nexport default function Page() {\n  return (\n    <div className=\"px-4 py-10 md:px-6 md:py-14\">\n      <CursorTerminal />\n    </div>\n  )\n}\n",
      "type": "registry:page",
      "target": "app/cursor-terminal/page.tsx"
    },
    {
      "path": "registry/new-york/blocks/cursor-terminal/components/cursor-terminal.tsx",
      "content": "\"use client\"\n\nimport {\n  ChevronDown,\n  ChevronUp,\n  MoreHorizontal,\n  Plus,\n  Terminal,\n  X,\n} from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\n\nimport { useTerminalAnimation } from \"../hooks/use-terminal-animation\"\nimport Spinner from \"./spinner\"\nimport PromptShell from \"./prompt-shell\"\nimport PanelIconButton from \"./panel-icon-button\"\nimport { ICON_SM, ICON_XS, PANEL_TABS, type LineColor } from \"../lib/config\"\n\nimport \"../styles/cursor-terminal.css\"\n\nfunction colorCls(c?: LineColor) {\n  if (c === \"green\") return \"text-[#16c60c]\"\n  if (c === \"cyan\") return \"text-[#61d6d6]\"\n  if (c === \"dim\") return \"text-[#767676]\"\n  return \"text-[#cccccc]\"\n}\n\nfunction renderFirstStepCommand(text: string) {\n  const npxToken = \"npx\"\n  const commandPrefix = \"npx shadcn@latest add \"\n\n  if (!text.startsWith(npxToken)) {\n    return <span className=\"min-w-0\">{text}</span>\n  }\n\n  const npxEnd = npxToken.length\n  const argStart = commandPrefix.length\n\n  return (\n    <span className=\"min-w-0\">\n      <span className=\"text-[#dcdcaa]\">{npxToken}</span>\n      {text.slice(npxEnd, Math.min(text.length, argStart))}\n      {text.length > argStart && (\n        <span className=\"text-[#4fc1ff]\">{text.slice(argStart)}</span>\n      )}\n    </span>\n  )\n}\n\nexport default function CursorTerminal({ className }: { className?: string }) {\n  const { lines, prompt, scrollRef } = useTerminalAnimation()\n\n  return (\n    <div\n      className={cn(\n        \"crt-terminal mx-auto w-full max-w-3xl min-w-0 overflow-hidden rounded-md border border-white/10 bg-[#1e1e1e] font-mono shadow-[0_20px_40px_-12px_rgb(0_0_0/0.4)]\",\n        className\n      )}\n    >\n      <div className=\"flex h-9 min-h-9 items-stretch border-b border-white/6 bg-[#252526] select-none\">\n        <div className=\"flex min-w-0 flex-1 items-stretch overflow-x-auto [&::-webkit-scrollbar]:hidden\">\n          {PANEL_TABS.map((name) => {\n            const active = name === \"Terminal\"\n            return (\n              <button\n                key={name}\n                type=\"button\"\n                aria-hidden\n                tabIndex={-1}\n                className={cn(\n                  \"shrink-0 border-t border-t-transparent px-3 text-[10px] transition-colors\",\n                  active\n                    ? \"border-t-[#007fd4] bg-[#1e1e1e] text-[#e8e8e8]\"\n                    : \"text-[#858585] hover:text-[#c0c0c0]\"\n                )}\n              >\n                {name}\n              </button>\n            )\n          })}\n        </div>\n\n        <div className=\"flex shrink-0 items-center gap-px pr-1 pl-2\">\n          <button\n            type=\"button\"\n            aria-hidden\n            tabIndex={-1}\n            className=\"flex h-7 items-center gap-1 rounded px-1.5 text-[10px] text-[#cccccc] transition-colors hover:bg-white/6\"\n          >\n            <Terminal className={cn(ICON_SM, \"text-[#3b8eea]\")} aria-hidden />\n            <span className=\"max-w-22 truncate sm:max-w-none\">powershell</span>\n            <ChevronDown\n              className={cn(ICON_XS, \"text-[#858585]\")}\n              aria-hidden\n            />\n          </button>\n          <PanelIconButton\n            label=\"New terminal\"\n            className=\"w-auto min-w-7 gap-0.5 px-1\"\n          >\n            <Plus className={ICON_SM} aria-hidden />\n            <ChevronDown\n              className={cn(ICON_XS, \"text-[#a0a0a0]\")}\n              aria-hidden\n            />\n          </PanelIconButton>\n          <PanelIconButton label=\"More\">\n            <MoreHorizontal className={ICON_SM} aria-hidden />\n          </PanelIconButton>\n          <PanelIconButton label=\"Maximize panel\">\n            <ChevronUp className={ICON_SM} aria-hidden />\n          </PanelIconButton>\n          <PanelIconButton label=\"Close panel\" hoverDanger>\n            <X className={ICON_SM} aria-hidden />\n          </PanelIconButton>\n        </div>\n      </div>\n\n      <div\n        ref={scrollRef}\n        className=\"crt-body h-88 overflow-y-auto bg-[#1e1e1e] px-3.5 py-2 text-left font-mono text-[11px] leading-[1.45] text-[#cccccc] [&::-webkit-scrollbar]:hidden\"\n        style={{ scrollbarWidth: \"none\" }}\n      >\n        {lines.map((line) =>\n          line.promptLine ? (\n            <div key={line.id}>\n              <div className=\"flex min-w-0 flex-wrap items-center gap-x-1 gap-y-0\">\n                <PromptShell dot={line.dot ?? \"grey\"} showPath />\n                {renderFirstStepCommand(line.text)}\n              </div>\n            </div>\n          ) : (\n            <div\n              key={line.id}\n              className={cn(\"crt-line-in\", colorCls(line.color))}\n            >\n              {line.spinning ? (\n                <span>\n                  <Spinner /> {line.text}\n                </span>\n              ) : (\n                line.text || \"\\u00A0\"\n              )}\n            </div>\n          )\n        )}\n\n        {prompt.showPrompt && (\n          <div\n            className={cn(\n              \"flex min-w-0 flex-wrap items-center gap-x-1 gap-y-0\",\n              prompt.animateIn && \"crt-line-in\"\n            )}\n          >\n            <PromptShell dot={prompt.dot} showPath={prompt.showPath} />\n            {prompt.typed && (\n              <span className={cn(\"min-w-0\", prompt.pasteIn && \"crt-paste-in\")}>\n                {renderFirstStepCommand(prompt.typed)}\n              </span>\n            )}\n            {prompt.cursor && (\n              <span\n                className=\"crt-cursor ml-px inline-block h-[1em] w-2 translate-y-px bg-[#cccccc] align-text-bottom\"\n                aria-hidden\n              />\n            )}\n          </div>\n        )}\n      </div>\n\n      <div className=\"border-t border-white/6 bg-[#1e1e1e] py-1.5 text-center text-[10px] text-[#6e6e6e]\">\n        Ctrl+K to generate command\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "app/cursor-terminal/components/cursor-terminal.tsx"
    },
    {
      "path": "registry/new-york/blocks/cursor-terminal/components/panel-icon-button.tsx",
      "content": "import type { ReactNode } from \"react\"\nimport { cn } from \"@/lib/utils\"\n\nexport default function PanelIconButton({\n  children,\n  label,\n  hoverDanger,\n  className,\n  onClick,\n  ariaPressed,\n  tabIndex = -1,\n}: {\n  children: ReactNode\n  label: string\n  hoverDanger?: boolean\n  className?: string\n  onClick?: () => void\n  ariaPressed?: boolean | \"mixed\"\n  tabIndex?: number\n}) {\n  return (\n    <button\n      type=\"button\"\n      aria-label={label}\n      title={label}\n      aria-pressed={ariaPressed}\n      tabIndex={tabIndex}\n      onClick={onClick}\n      className={cn(\n        \"flex size-7 items-center justify-center rounded text-[#c8c8c8] transition-colors hover:bg-white/6\",\n        hoverDanger && \"hover:bg-[rgb(196_43_28/0.9)] hover:text-white\",\n        className\n      )}\n    >\n      {children}\n    </button>\n  )\n}\n",
      "type": "registry:component",
      "target": "app/cursor-terminal/components/panel-icon-button.tsx"
    },
    {
      "path": "registry/new-york/blocks/cursor-terminal/components/prompt-shell.tsx",
      "content": "import { PROMPT } from \"../lib/config\"\nimport type { PromptState } from \"../lib/config\"\nimport { cn } from \"@/lib/utils\"\n\ntype Props = Pick<PromptState, \"dot\" | \"showPath\">\n\nexport default function PromptShell({ dot, showPath }: Props) {\n  return (\n    <div className=\"flex items-center gap-2\">\n      <span\n        className={cn(\n          \"inline-block size-[7px] shrink-0 rounded-full ring-1 ring-white/15\",\n          dot === \"blue\" ? \"bg-[#3b8eea]\" : \"bg-[#767676]\"\n        )}\n        aria-hidden\n      />\n      {showPath && <span className=\"tabular-nums\">{PROMPT}</span>}\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "app/cursor-terminal/components/prompt-shell.tsx"
    },
    {
      "path": "registry/new-york/blocks/cursor-terminal/components/spinner.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useState } from \"react\"\n\nconst FRAMES = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"]\n\nexport default function Spinner() {\n  const [frame, setFrame] = useState(0)\n  useEffect(() => {\n    const t = setInterval(() => setFrame((f) => (f + 1) % FRAMES.length), 80)\n    return () => clearInterval(t)\n  }, [])\n  return <span>{FRAMES[frame]}</span>\n}\n",
      "type": "registry:component",
      "target": "app/cursor-terminal/components/spinner.tsx"
    },
    {
      "path": "registry/new-york/blocks/cursor-terminal/hooks/use-terminal-animation.ts",
      "content": "// Phase flow: idle → typing (paste) → output → done → idle (loops)\nimport { useEffect, useMemo, useRef, useState } from \"react\"\n\nimport {\n  COMMAND, INIT_LINES, OUTPUT_SEQUENCE, PASTE_SETTLE_MS,\n  type Line, type Phase, type PromptState,\n} from \"../lib/config\"\n\nexport function useTerminalAnimation() {\n  const [lines, setLines] = useState<Line[]>(INIT_LINES)\n  const [typed, setTyped] = useState(\"\")\n  const [phase, setPhase] = useState<Phase>(\"idle\")\n  const [outIdx, setOutIdx] = useState(0)\n  const idRef = useRef(10)\n  const spinTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n  const scrollRef = useRef<HTMLDivElement>(null)\n\n  useEffect(() => {\n    if (scrollRef.current) {\n      scrollRef.current.scrollTop = scrollRef.current.scrollHeight\n    }\n  }, [lines, typed])\n\n  useEffect(() => {\n    if (phase !== \"idle\") return\n    const t = setTimeout(() => {\n      setTyped(COMMAND)\n      setPhase(\"typing\")\n    }, 1800)\n    return () => clearTimeout(t)\n  }, [phase])\n\n  useEffect(() => {\n    if (phase !== \"typing\") return\n    const t = setTimeout(() => {\n      setLines((prev) => [...prev, { id: idRef.current++, text: COMMAND, promptLine: true, dot: \"grey\" }])\n      setTyped(\"\")\n      setOutIdx(0)\n      setPhase(\"output\")\n    }, PASTE_SETTLE_MS)\n    return () => clearTimeout(t)\n  }, [phase])\n\n  useEffect(() => {\n    if (phase !== \"output\") return\n    if (outIdx >= OUTPUT_SEQUENCE.length) {\n      const t = setTimeout(() => setPhase(\"done\"), 500)\n      return () => clearTimeout(t)\n    }\n    const item = OUTPUT_SEQUENCE[outIdx]\n    if (item.type === \"spinner\") {\n      const t = setTimeout(() => {\n        const spinId = idRef.current++\n        setLines((prev) => [...prev, { id: spinId, text: item.text, spinning: true }])\n        spinTimerRef.current = setTimeout(() => {\n          setLines((prev) =>\n            prev.map((l) =>\n              l.id === spinId\n                ? { ...l, spinning: false, text: `✓ ${item.doneText ?? item.text}`, color: \"green\" as const }\n                : l,\n            ),\n          )\n          setOutIdx((i) => i + 1)\n        }, item.duration)\n      }, item.delay)\n      return () => {\n        clearTimeout(t)\n        if (spinTimerRef.current) clearTimeout(spinTimerRef.current)\n      }\n    }\n    const t = setTimeout(() => {\n      setLines((prev) => [...prev, { id: idRef.current++, text: item.text, color: item.color }])\n      setOutIdx((i) => i + 1)\n    }, item.delay)\n    return () => clearTimeout(t)\n  }, [phase, outIdx])\n\n  useEffect(() => {\n    if (phase !== \"done\") return\n    const blueTimer = setTimeout(() => {\n      setLines((prev) => prev.map((line) => (line.promptLine ? { ...line, dot: \"blue\" } : line)))\n    }, 700)\n    return () => clearTimeout(blueTimer)\n  }, [phase])\n\n  const prompt = useMemo<PromptState>(() => {\n    switch (phase) {\n      case \"idle\":\n        return { dot: \"grey\", showPrompt: true, showPath: true, typed: \"\", cursor: true, animateIn: false, pasteIn: false }\n      case \"typing\":\n        return { dot: \"grey\", showPrompt: true, showPath: true, typed, cursor: true, animateIn: false, pasteIn: true }\n      case \"output\":\n        return { dot: \"grey\", showPrompt: false, showPath: false, typed: \"\", cursor: false, animateIn: false, pasteIn: false }\n      case \"done\":\n        return {\n          dot: \"grey\",\n          showPrompt: true,\n          showPath: true,\n          typed: \"\",\n          cursor: false,\n          animateIn: true,\n          pasteIn: false,\n        }\n    }\n  }, [phase, typed])\n\n  return { lines, prompt, scrollRef }\n}\n",
      "type": "registry:hook",
      "target": "app/cursor-terminal/hooks/use-terminal-animation.ts"
    },
    {
      "path": "registry/new-york/blocks/cursor-terminal/lib/config.ts",
      "content": "export type LineColor = \"green\" | \"cyan\" | \"dim\"\n\nexport type Phase = \"idle\" | \"typing\" | \"output\" | \"done\"\n\nexport interface PromptState {\n  dot: \"blue\" | \"grey\"\n  showPrompt: boolean\n  showPath: boolean\n  typed: string\n  cursor: boolean\n  animateIn: boolean\n  pasteIn: boolean\n}\n\nexport interface Line {\n  id: number\n  text: string\n  color?: LineColor\n  spinning?: boolean\n  promptLine?: boolean\n  dot?: \"blue\" | \"grey\"\n}\n\nexport type OutputItem =\n  | { type: \"line\"; text: string; color?: LineColor; delay: number }\n  | { type: \"spinner\"; text: string; doneText?: string; delay: number; duration: number }\n\nexport const PROMPT = \"PS C:\\\\projects\\\\my-app> \"\nexport const COMMAND = 'npx shadcn@latest add \"https://uiception.com/r/hero-section-v1.json\"'\n/** Hold after paste before the command line commits. */\nexport const PASTE_SETTLE_MS = 580\n\nexport const ICON_SM = \"size-3 shrink-0 stroke-[1.75]\"\nexport const ICON_XS = \"size-2.5 shrink-0 stroke-[1.75]\"\n\nexport const PANEL_TABS = [\"Problems\", \"Output\", \"Debug Console\", \"Terminal\", \"Ports\", \"GitLens\"] as const\n\nexport const INIT_LINES: Line[] = []\n\nexport const OUTPUT_SEQUENCE: OutputItem[] = [\n  { type: \"spinner\", text: \"Checking registry.\", delay: 420, duration: 1500 },\n  {\n    type: \"spinner\",\n    text: \"Installing dependencies.\",\n    delay: 240,\n    duration: 3000,\n    doneText: \"Installed dependencies.\",\n  },\n  { type: \"line\", text: \"✓ Created 33 files:\", color: \"green\", delay: 320 },\n  { type: \"line\", text: \"  - components/ui/button.tsx\", color: \"cyan\", delay: 155 },\n  { type: \"line\", text: \"  - app/hero-section-v1/page.tsx\", color: \"cyan\", delay: 145 },\n  { type: \"line\", text: \"  - app/hero-section-v1/components/hero-section-v1.tsx\", color: \"cyan\", delay: 150 },\n  { type: \"line\", text: \"  - app/brands-section-v1/components/brands-section-v1.tsx\", color: \"cyan\", delay: 142 },\n  { type: \"line\", text: \"  - app/mac-os-terminal/components/mac-os-terminal.tsx\", color: \"cyan\", delay: 148 },\n  { type: \"line\", text: \"  - public/images/blocks/hero-section-v1/image.png.gitkeep\", color: \"cyan\", delay: 152 },\n  {\n    type: \"line\",\n    text: \"  … +27 files (block-media, terminal TS/CSS, code segments, 11 brand SVGs)\",\n    color: \"dim\",\n    delay: 190,\n  },\n  { type: \"line\", text: \"\", delay: 260 },\n  { type: \"line\", text: \"Success! Component installed successfully.\", color: \"green\", delay: 300 },\n  { type: \"line\", text: \"You may now import and use the component.\", color: \"dim\", delay: 180 },\n]\n",
      "type": "registry:lib",
      "target": "app/cursor-terminal/lib/config.ts"
    },
    {
      "path": "registry/new-york/blocks/cursor-terminal/styles/cursor-terminal.css",
      "content": "/* cursor-terminal — caret blink, line fade-in, paste-in */\n\n.crt-cursor {\n  animation: crt-terminal-blink 1.05s step-end infinite;\n  margin-left: 1px;\n  display: inline-block;\n  height: 1em;\n  width: 0.5rem;\n  translate: 0 1px;\n  vertical-align: text-bottom;\n  background-color: var(--crt-t-cursor-bg, #cccccc);\n}\n\n.crt-line-in {\n  animation: crt-terminal-line-fade 0.07s linear both;\n}\n\n.crt-paste-in {\n  animation: crt-terminal-paste-in 0.42s cubic-bezier(0.2, 1, 0.32, 1) both;\n}\n\n@keyframes crt-terminal-blink {\n  0%,\n  48% {\n    opacity: 1;\n  }\n  52%,\n  100% {\n    opacity: 0;\n  }\n}\n\n@keyframes crt-terminal-line-fade {\n  from {\n    opacity: 0.35;\n  }\n  to {\n    opacity: 1;\n  }\n}\n\n@keyframes crt-terminal-paste-in {\n  from {\n    clip-path: inset(0 100% 0 0);\n  }\n  to {\n    clip-path: inset(0 0% 0 0);\n  }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .crt-cursor {\n    animation: none;\n    opacity: 1;\n  }\n\n  .crt-line-in,\n  .crt-paste-in {\n    animation: none;\n  }\n}\n",
      "type": "registry:file",
      "target": "app/cursor-terminal/styles/cursor-terminal.css"
    }
  ],
  "type": "registry:block"
}